package com.nulldev.util.web.YouTubeExtractor;

import java.util.List;
import java.util.Map;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.data.Arrays.arrays.FastLists;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.manage.Unimplemented;
import com.nulldev.util.manage.Version;
import com.nulldev.util.web.URLUtils.Entry;
import com.nulldev.util.web.YouTubeExtractor.dfs_fix.CachedDFSFixer;
import com.nulldev.util.web.YouTubeExtractor.extractors.IYouTubeExtractor;
import com.nulldev.util.web.YouTubeExtractor.extractors.next.YouTubeEmbedNextExtractor;
import com.nulldev.util.web.YouTubeExtractor.extractors.next.YouTubeGVIExtractor;
import com.nulldev.util.web.YouTubeExtractor.extractors.next.YouTubeNextFEE;
import com.nulldev.util.web.YouTubeExtractor.extractors.next.YouTubeNextFEEMT;
import com.nulldev.util.web.YouTubeExtractor.extractors.next_rolling.YouTubeNextFEERolling;
import com.nulldev.util.web.YouTubeExtractor.extractors.third_party.InvidiousExtractor;
import com.nulldev.util.web.YouTubeExtractor.extractors.third_party.UnAgeRestrictorExtractor;
import com.nulldev.util.web.YouTubeExtractor.extractors.util.YTIAPIClientType;
import com.nulldev.util.web.YouTubeExtractor.extractors.util.YTIAPIClientType.YTIAPIClientEnums;
import com.nulldev.util.web.YouTubeExtractor.extractors.ytapi.YouTubeInternalAPIExtractor;
import com.nulldev.util.web.YouTubeExtractor.extractors.ytapi_rolling.YouTubeAPIExtractorRolling;
import com.nulldev.util.web.YouTubeExtractor.extractors.ytiapi_rolling.YouTubeIAPIExtractorRolling;
import com.nulldev.util.web.YouTubeExtractor.signatures.CachedSignatureExtractor;

public class YouTubeExtractor {
	/*
	 * YTE's changelog has been moved to the CHANGELOG file.
	 */

	public static final String VERSION = "2.2.3";
	public static final Version LIB_VERSION = new Version(VERSION);

	private static final Map<String, IYouTubeExtractor> extractors = FastMaps.map();

	public static final String defaultExtractor = "YTE.next.nextFEE";
	public static final String defaultEmbedExtractor = "YTE.next.embedFEE";
	public static final String defaultThirdPartyExtractor = "YTE.third_party.invidious";

	/* Cookie Section */

	public static final String CONSENT_TEXT_CATCH = "https://consent.youtube.com/d?continue\\u003d";

	/**
	 * YTE by default picks the "Reject All" option.
	 */
	public static final Entry[] DEFAULT_COOKIES = new Entry[]
		{ new Entry("Cookie", "SOCS=CAA; PREF=tz=UTC&f6=40000000") };
	/**
	 * Fallback for the few pages that demand cookie consent.
	 */
	public static final Entry[] DEFAULT_COOKIES2 = new Entry[]
		{ new Entry("Cookie", "SOCS=CAI; PREF=tz=UTC&f6=40000000") };
	public static final Entry[] BLANK_COOKIES = new Entry[0];

	/* */

	public static enum ABIFeatures {
		/* v1 */

		/**
		 * This extractor supports returning an actual value and not a null pointer.
		 */
		VIDEO_EXTRACTION(1000),
		/**
		 * The extractor can return the following types of metadata:
		 * <ul>
		 * <li>Video Title</li>
		 * <li>Video Author</li>
		 * </ul>
		 */
		VIDEO_BASIC_METADATA(1100),
		/**
		 * The extractor can handle YouTube playlists and return a valid Playlist
		 * object.
		 */
		VIDEO_PLAYLIST_SUPPORT(1200),
		/**
		 * This extractor can return related data <b>OR</b> interface with the related
		 * parser by providing the required data for it.
		 */
		VIDEO_RELATED_CONTENT_SUPPORT(1300),
		/**
		 * The extractor will ask the EMBED API if the video is age-gated. <br>
		 * <br>
		 * <b>NOTE:</b> This is an old approach and has been since replaced with the
		 * AGE-GATE API.
		 */
		VIDEO_FALLBACK_EMBED(1400),
		/**
		 * The extractor is capable of bypassing controversy warnings.
		 */
		VIDEO_CONTROVERSY_BYPASS(1500),

		/* v2 */

		/**
		 * This extractor can decipher videos with signature protections.
		 */
		VIDEO_DECIPHERING(2000),
		/**
		 * The extractor supports subtitles/closed-captions and will return basic
		 * captions, lacking formatting.
		 */
		VIDEO_CAPTIONS_BASIC(2100),
		/**
		 * The same as <code>VIDEO_CAPTIONS_BASIC</code> but with formatting support.
		 */
		VIDEO_CAPTIONS_FULL(2200),
		/**
		 * The same as <code>VIDEO_PLAYLIST_SUPPORT</code> with the following changes:
		 * <br>
		 * <ul>
		 * <li>Support for larger playlists above 200 videos</li>
		 * </ul>
		 */
		VIDEO_PLAYLIST_ENHANCED(2300),
		/**
		 * The extractor returns like and dislike (not anymore lol) values. <br>
		 * <br>
		 * Dislikes are no longer required due to changes in YouTube. <br>
		 * [<span style="color: orange">!</span>] Claims about YouTube have been proven
		 * false by our fact-checkers.
		 */
		VIDEO_LIKE_DISLIKE_RATIO(2400),

		/* v3 */

		/**
		 * This extractor can detect 360 degree content and flag it as such. <br>
		 * It is still up to the user to handle the content correctly.
		 */
		VIDEO_360_CONTENT(3000),
		/**
		 * The extractor can cooperate with the global decipherer and update it, if
		 * required.
		 */
		VIDEO_DECIPHERER_AUTO(3100),
		/**
		 * The extractor can flag if a video isn't live yet and provide information
		 * about it.
		 */
		VIDEO_UPCOMING_VIDEO_HANDLE(3200),
		/**
		 * This extractor can handle live/DVR content and provide HLS/DASH/YT format
		 * streams.
		 */
		VIDEO_LIVESTREAMS(3300),
		/**
		 * The extractor will handle various ciphers and can switch between versions
		 * seamlessly.
		 */
		VIDEO_MULTI_TYPE_CIPHERS(3400),
		/**
		 * This extractor will provide endscreen entries, which are displayed at the end
		 * of a video.
		 */
		VIDEO_ENDSCREEN(3500),

		/* v4 */

		/**
		 * The extractor will provide the data required for loading comments via the
		 * YouTubeExtractor.
		 */
		COMMENTS_FULL_SUPPORT(4000),
		/**
		 * The extractor will provide cookies and store them in order to track sessions.
		 * <br>
		 * <br>
		 * <b>WARNING:</b> The API and behaviour hasn't really been implemented in any
		 * extractor yet. (as of 2.1.20)
		 */
		@Unimplemented
		COOKIE_STORAGE(4100, true),
		/**
		 * The extractor will use multi-threading in order to improve overall
		 * performance and extraction times.
		 */
		EXTRACTOR_MULTI_THREADING(4200),
		/**
		 * The extractor will avoid using legacy parsing methods involving HTML parsing.
		 */
		EXTRACTOR_NO_HTML_PARSING(4300),
		/**
		 * This extractor will use threadIt! v4 instead of v3 and older versions of
		 * threadIt! <br>
		 * <br>
		 * SIDENOTE: This was implemented before the planned removal of v3.
		 */
		EXTRACTOR_V4_THREADING(4400),
		/**
		 * This extractor will provide clarifications (""""""fact""""""-checking) on
		 * request. <br>
		 * <br>
		 * <b>This API is experimental.</b>
		 */
		CLARIFICATIONS(4500, true),
		/**
		 * The extractor supports chapters within videos and provides the API to access
		 * them.
		 */
		CHAPTER_SUPPORT(4510),
		/**
		 * The extractor provides super tags which are shown above the video title,
		 * typically as "hashtags".
		 */
		SUPER_TAG_SUPPORT(4520),

		/* v5 */

		/**
		 * This extractor provides YouTube's storyboards for content preview during
		 * seek.
		 */
		INLINE_VIDEO_THUMBNAILS(5000), // Also known as storyboards.
		/**
		 * The same as <code>INLINE_VIDEO_THUMBNAILS</code> but with the following
		 * changes: <br>
		 * <ul>
		 * <li>Sections</li>
		 * <li>Quality Options</li>
		 * <li>Positioning Info.</li>
		 * </ul>
		 */
		INLINE_VIDEO_THUMBNAILS_V2(5005), // Enhanced storyboards with positioning and sections.
		/**
		 * The same as <code>INLINE_VIDEO_THUMBNAILS</code> and
		 * <code>INLINE_VIDEO_THUMBNAILS_V2</code> but without any form of processing,
		 * just the raw payload is provided for use with other parsers or other tools.
		 */
		INLINE_RAW_VIDEO_THUMBNAILS(5010), // Unprocessed storyboards
		/**
		 * This extractor provides an API to check what thumbnail quality levels are
		 * available and can be used.
		 */
		THUMBNAIL_SUPPORT_LEVEL(5020),
		/**
		 * This extractor provides raw captions/subtitles/closed-captions that have no
		 * processing or proxying done to them.
		 */
		RAW_CAPTION_URLS(5030),
		/**
		 * The extractor provides links to custom thumbnails.
		 */
		CUSTOM_THUMBNAIL_URLS(5050),
		/**
		 * This extractor can delay extraction on request and process data later than
		 * normal. <br>
		 * Functions: awaitCompletion() <br>
		 * Stages that would be delayed <br>
		 * - Signature decipher <br>
		 * - ECD (Extended Codec Data) <br>
		 * - Comments <br>
		 * - Storyboards <br>
		 * - Playlists <br>
		 * - Chapters
		 */
		@Unimplemented
		POST_EXTRACTION_PROCESSING(5100, true),
		/**
		 * This extractor will provide enhanced codec metadata like Codec Profiles and
		 * more.
		 */
		EXTENDED_CODEC_METADATA(5200),
		/**
		 * The extractor will provide DRM metadata and payloads when available.
		 */
		DRM_METADATA(5300),
		/**
		 * This extractor is capable of delaying the process of deciphering.
		 */
		CIPHER_DEFER(5310),

		/* v5.5 */

		/**
		 * This extractor supports the new 2.2 and higher ABI for YouTubeExtractor. <br>
		 * Mandatory as of YouTubeExtractor 2.2.
		 */
		YTE_API_2_2_SUPPORT(5500),
		/**
		 * The extractor can hint what type of playback mode should be used.
		 */
		VIDEO_PLAYBACK_MODE(5510),

		/* v6 */

		/**
		 * This extractor supports multiple ways of interfacing with YouTube like the
		 * mobile and TV APIs.
		 */
		MULTI_CLIENT_SUPPORT(6000),
		/**
		 * The extractor will use accelerated logic in order to process and generate
		 * YouTubeVideo objects.
		 */
		FAST_VIDEO_GENERATION(6010),
		/**
		 * This extractor will provide during video links from the (i) icon. <br>
		 * <br>
		 * User annotations are technically dead :( <br>
		 * However, video links and redirects with the (i) icon still count I guess.
		 */
		VIDEO_ANNOTATIONS(6020),
		/**
		 * The extractor will provide the sub-description. (music copyright and other
		 * information)
		 */
		SUBDESCRIPTIONS(6030),
		/**
		 * This extractor supports YTE's flags API introduced in ABI 2.2
		 */
		VIDEO_FLAGS(6040),
		/**
		 * This extractor supports YouTube's viewer heatmaps.
		 */
		VIDEO_HEATMAP(6050),
		/**
		 * This extractor can provide the start time of content/live-stream as a
		 * timestamp.
		 */
		VIDEO_START_TIME(6055),
		/**
		 * This extractor can provide text that is translated into the local
		 * localization.
		 */
		VIDEO_LOCALIZED_TEXT(6060),
		/**
		 * This extractor can provide the data needed to update the metadata of a
		 * livestream/event.
		 */
		VIDEO_UPDATED_METADATA(6070),
		/**
		 * The extractor uses the enum <code>ThreeSixtyMode</code> per-video instead of
		 * the global <code>is360</code> and <code>isVR180</code> boolean flags.
		 */
		VIDEO_PROJECTION_MODE(6080),
		/**
		 * The extractor provides the required information to load and/or query an
		 * advertisement. <br>
		 * <b>YTE does not load or handle advertisements. This is only metadata.</b>
		 * <br>
		 * <b>Users like nTube should handle this internally, separate to YTE.</b>
		 */
		VIDEO_ADVERTISEMENT_METADATA(6090, true),

		;

		public final int version;
		public final boolean isExperimental;

		private ABIFeatures(final int version) {
			this(version, false);
		}

		private ABIFeatures(final int version, final boolean isExperimental) {
			this.version = version;
			this.isExperimental = isExperimental;
		}

		public boolean isSupported(final int version) {
			return this.version >= version;
		}

		public boolean isExperimentalABI() {
			return this.isExperimental;
		}
	}

	static {
		if (extractors.isEmpty()) {
			extractors.put("YTE.next.embedFEE", new YouTubeEmbedNextExtractor());
			extractors.put("YTE.next.nextFEE", new YouTubeNextFEE());
			extractors.put("YTE.next.nextFEE.MT", new YouTubeNextFEEMT());
			extractors.put("YouTubeExtractor.NFEE_rolling_release", new YouTubeNextFEERolling());
			extractors.put("ytapi.native", new YouTubeInternalAPIExtractor());
			extractors.put("ytapi.native-rolling", new YouTubeAPIExtractorRolling());
			extractors.put("ytiapi.native-rolling", new YouTubeIAPIExtractorRolling());
			extractors.put("YTE.next.video_info_extractor", new YouTubeGVIExtractor());
			extractors.put("YTE.third_party.invidious", new InvidiousExtractor());
			extractors.put("YTE.third_party.simple_age_restriction_bypass", new UnAgeRestrictorExtractor());
		}
	}

	public static final CachedSignatureExtractor extractor = new CachedSignatureExtractor();
	public static final CachedDFSFixer dfs_handler = new CachedDFSFixer();

	/*
	 * The extractors are in another castle as of 2.1! [comedy] They are now located
	 * in com.nulldev.util.web.YouTubeExtractor.extractors
	 */

	public static boolean isYouTubeID(final String videoID) {
		if (videoID == null || videoID.isEmpty())
			return false;
		return videoID.length() < 12;
	}

	public static void __static_call() {
	}

	public static final String[] EXTRACTORS =
		{
				/* legacy has been removed in 2.1.19 */
				/* next */
				"YTE.next.embedFEE", "YTE.next.nextFEE", "YTE.next.nextFEE.MT",
				/* next_rolling */
				"YouTubeExtractor.NFEE_rolling_release",
				/* ytapi */
				"ytapi.native",
				/* ytapi_rolling */
				"ytapi.native-rolling",
				/* ytIapi */
				"ytiapi.native-rolling",
				/* flex */
				/* temporary: GVI */
				"YTE.next.video_info_extractor",
				/* third party */
				"YTE.third_party.invidious", "YTE.third_party.simple_age_restriction_bypass" };

	public static enum ExtractorType {
		EMBEDDED("embed"),
		PAGE("page"),
		THIRD_PARTY("tp"),
		AGE_GATE("age_gate")

		;

		public final String et;

		private ExtractorType(final String et) {
			this.et = et;
		}

		public static ExtractorType get(final String et) {
			if (et == null || et.isEmpty())
				return PAGE;
			for (final ExtractorType type : values()) {
				if (type.et.equalsIgnoreCase(et))
					return type;
			}
			return PAGE;
		}
	}

	public static IYouTubeExtractor get(final ExtractorType type) {
		switch (type) {
			case EMBEDDED: {
				if (Arguments.hasArgument("--NTubeServer.useNFEEe", "--YouTubeExtractor.useNFEEe"))
					return extractors.get("YTE.next.embedFEE");
				return extractors.get(defaultEmbedExtractor);
			}
			case PAGE: {
				if (Arguments.hasArgument("--NTubeServer.useYTAPI", "--YouTubeExtractor.useYTAPI"))
					return extractors.get("ytapi.native");
				if (Arguments.hasArgument("--NTubeServer.useYTAPIRolling", "--YouTubeExtractor.useYTAPIRolling"))
					return extractors.get("ytapi.native-rolling");
				if (Arguments.hasArgument("--NTubeServer.enableNFR", "--YouTubeExtractor.enableNFR"))
					return extractors.get("YouTubeExtractor.NFEE_rolling_release");
				if (Arguments.hasArgument("--NTubeServer.useYTAPII", "--YouTubeExtractor.useYTAPII"))
					return extractors.get("ytiapi.native-rolling");
				if (Arguments.hasArgument("--NTubeServer.useNFEEMT", "--YouTubeExtractor.useNFEEMT"))
					return extractors.get("YTE.next.nextFEE.MT");
				if (Arguments.hasArgument("--NTubeServer.useNFEE", "--YouTubeExtractor.useNFEE"))
					return extractors.get("YTE.next.nextFEE");
				if (Arguments.hasArgument("--NTubeServer.useGVI", "--YouTubeExtractor.useGVI"))
					return extractors.get("YTE.next.video_info_extractor");
				return extractors.get(defaultExtractor);
			}
			case THIRD_PARTY: {
				if (Arguments.hasArgument("--NTubeSever.useInvidious", "--YouTubeExtractor.useInvidious"))
					return extractors.get("YTE.third_party.invidious");
				return extractors.get(defaultThirdPartyExtractor);
			}
			case AGE_GATE: {
				for (final java.util.Map.Entry<String, IYouTubeExtractor> es : extractors.entrySet()) {
					final IYouTubeExtractor ex = es.getValue();
					if (ex == null)
						continue;
					if (((int) ex.getFlag(IYouTubeExtractor.FLAG_EXTRACTOR_SUPPORTS_AGE_GATE)) == IYouTubeExtractor.FLAG_TRUE)
						return ex;
				}
				return extractors.get(defaultEmbedExtractor);
			}
			default: {
				return extractors.get(defaultExtractor);
			}
		}
	}

	public static IYouTubeExtractor get(final String id) {
		if (id == null || id.isEmpty())
			extractors.get(defaultExtractor);
		if (extractors.containsKey(id))
			return extractors.get(id);
		return extractors.get(defaultExtractor);
	}

	public static Entry[] makeHeaders() {
		return makeHeaders(null);
	}

	public static Entry[] makeHeaders(final YTIAPIClientType type) {
		final boolean hasType = (type != null);
		final List<Entry> entries = FastLists.list(4);
		entries.add(new Entry("X-YouTube-Client-Name", hasType ? type.get(YTIAPIClientEnums.ClientName) : "1"));
		entries.add(new Entry("X-Youtube-Client-Version",
				hasType && type != YTIAPIClientType.WEB ? type.get(YTIAPIClientEnums.FallbackVersion) : YouTubeExtractorConstants.CLIENT_VERSION));
		entries.add(new Entry("X-Youtube-Bootstrap-Logged-In", "false"));
		entries.add(new Entry("X-Goog-EOM-Visitor-Id", YouTubeExtractorConstants.VISITOR_DATA));
		return entries.toArray(new Entry[entries.size()]);
	}
}
